package com.ala4.oxcafe.boot.manager;

import com.ala4.oxcafe.boot.authentication.CustomAuthenticationToken;
import com.ala4.oxcafe.boot.dto.TokenDTO;
import com.ala4.oxcafe.boot.exception.LoginUserNotFoundException;
import com.ala4.oxcafe.boot.exception.ObtainUserException;
import com.ala4.oxcafe.boot.exception.SoloLoginException;
import com.ala4.oxcafe.common.utils.RedisUtil;
import com.ala4.oxcafe.common.utils.id.IdUtil;
import com.ala4.oxcafe.common.utils.object.ObjectUtil;
import com.ala4.oxcafe.common.utils.text.StrUtil;
import com.ala4.oxcafe.constants.SecurityConstants;
import com.ala4.oxcafe.properties.SecurityProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.Objects;

/**
 * 用户token管理服务
 *
 * @author PING
 * @version 1.0
 * @date 2024/12/25 11:02
 */
@Slf4j
@Component
public class RedisUserTokenManager implements UserTokenManager {

    private final SecurityProperties securityProperties;

    public RedisUserTokenManager(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }

    /**
     * 用户登录创建token
     *
     * @param customUserDetails 用户信息
     * @return token
     */
    @Override
    public TokenDTO createToken(CustomUserDetails customUserDetails) {
        // 创建token
        customUserDetails.setToken(IdUtil.fastUUID());
        // 创建token
        TokenDTO tokenDTO = new TokenDTO(customUserDetails.getUserId(), customUserDetails.getToken(), SecurityConstants.HEADER_UPP_BEARER, securityProperties.getTokenExpiresIn(), "", customUserDetails);

        Long tokenExpiresIn = securityProperties.getTokenExpiresIn();
        // 设置用户的过期时间
        customUserDetails.setExpiresAt(customUserDetails.getIssuedAt().plusSeconds(tokenExpiresIn));
        // 用户token 和 用户信息
        RedisUtil.set(SecurityConstants.USER_LOGIN_TOKEN_INFO + customUserDetails.getToken(), customUserDetails, tokenExpiresIn);

        // 如果不允许多终端登录 那么需要设置当前登录的token,否则不需要设置
        if (!securityProperties.getSoloLogin()) {
            // 当前的登录用户
            RedisUtil.set(SecurityConstants.USER_LOGIN_KEY + customUserDetails.getUserId(), tokenDTO.getAccess_token(), tokenExpiresIn);
        }
        return tokenDTO;
    }

    @Override
    public TokenDTO createToken(User user) {
        return createToken(new CustomUserDetails(user));
    }

    private CustomUserDetails checkLoginUserTokenGet(String token) {
        // 此处从redis获取到用户信息
        log.debug("checkLoginUserToken:{}", token);

        try {
            // 从REDIS获取用户信息
            CustomUserDetails customUserDetails = RedisUtil.get(SecurityConstants.USER_LOGIN_TOKEN_INFO + token, CustomUserDetails.class);
            if (Objects.isNull(customUserDetails)) {
                throw new LoginUserNotFoundException("用户已过期,请重新登录");
            }
            // 判断是否是黑名单用户
            Long blockUserId = RedisUtil.get(SecurityConstants.USER_LOGIN_TOKEN_BLOCK + customUserDetails.getUserId(), Long.class);
            if (ObjectUtil.isNotNull(blockUserId)) {
                // 删除用户信息
                RedisUtil.del(SecurityConstants.USER_LOGIN_TOKEN_INFO + token, SecurityConstants.USER_LOGIN_TOKEN_BLOCK + customUserDetails.getUserId());
                // 直接让用户重新登录
                throw new LoginUserNotFoundException("用户已过期,请重新登录");
            }

            // 校验多终端登录
            if (!securityProperties.getSoloLogin()) {
                String redisOldToken = RedisUtil.get(SecurityConstants.USER_LOGIN_KEY + customUserDetails.getUserId(), String.class);
                // 如果当前token 和redis的token不一致 说明被挤下线了
                if (!token.equals(redisOldToken)) {
                    RedisUtil.del(SecurityConstants.USER_LOGIN_TOKEN_INFO + token);
                    throw new SoloLoginException("您已在其他地方登录");
                }
            }

            // 是否自动续期
            if (securityProperties.getTokenRenewal()) {
                this.autoRenewalToken(customUserDetails, token);
            }
            return customUserDetails;
        } catch (AuthenticationException authenticationException) {
            throw authenticationException;
        } catch (Exception exception) {
            log.error("转换用户信息出现异常", exception);
            throw new ObtainUserException("转换用户信息异常-请联系管理员", exception);
        }
    }

    /**
     * 用户登出 从redis清楚token
     *
     * @param token token
     */
    @Override
    public void offlineUser(String token) {
        if (StrUtil.isEmpty(token)) {
            return;
        }

        CustomUserDetails customUserDetails = RedisUtil.get(SecurityConstants.USER_LOGIN_TOKEN_INFO + token, CustomUserDetails.class);

        if (!Objects.isNull(customUserDetails)) {
            // 清除停用用户的TOKEN
            RedisUtil.del(SecurityConstants.USER_LOGIN_TOKEN_INFO + token,
                    SecurityConstants.USER_LOGIN_KEY + customUserDetails.getUserId());
        }
    }

    @Override
    public void offlineUser(Long userId) {
        RedisUtil.set(SecurityConstants.USER_LOGIN_TOKEN_BLOCK + userId, userId, 3600L);
    }

    @Override
    public void createCaptchaCode(String captchaId, String captchaCode) {
        log.info("创建验证码:{}-{}", captchaId, captchaCode);
        RedisUtil.set(captchaId, captchaCode, securityProperties.getCaptchaTimeout());
    }

    @Override
    public String getCaptchaCode(String captchaId) {
        return RedisUtil.get(captchaId, String.class);
    }

    @Override
    public AbstractAuthenticationToken checkLoginUserTokenA(String token) {
        CustomUserDetails customUserDetails = this.checkLoginUserTokenGet(token);
        return new CustomAuthenticationToken(token, customUserDetails, customUserDetails.getAuthorities());
    }

    /**
     * 用户自动续期
     *
     * @param customUserDetails 用户信息
     * @param token             token
     */
    private void autoRenewalToken(CustomUserDetails customUserDetails, String token) {
        Instant expiresAt = customUserDetails.getExpiresAt();

        Instant instant = expiresAt.plusSeconds(-1800);
        // 用户失效半个小时内 如果有操作那就自动续期
        if (ObjectUtil.isNull(expiresAt) || Instant.now().isAfter(instant)) {
            Long tokenExpiresIn = securityProperties.getTokenExpiresIn();
            // 重新设置用户过期时间
            customUserDetails.setExpiresAt(Instant.now().plusSeconds(tokenExpiresIn));

            RedisUtil.set(SecurityConstants.USER_LOGIN_TOKEN_INFO + token, customUserDetails, tokenExpiresIn);
            // 当前的登录用户
            RedisUtil.set(SecurityConstants.USER_LOGIN_KEY + customUserDetails.getUserId(), token, tokenExpiresIn);
        }
    }
}
